﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml;
using System.Text.RegularExpressions;
using System.Web;

namespace HtmlToXml
{
    public class HtmlDoc : List<String> 
    {
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (String s in this)
                sb.Append(s);
            return sb.ToString();
        }
    }

    public class XmlUtil
    {
        public const string MyXmlDocumentTagName = "MyXmlDocument";

        public static String DownloadHtml(String url)
        {
            WebClient wc = new WebClient();
            return wc.DownloadString(url);
        }

        public static XmlDocument CreateXmlDocumentFromHtml(String html)
        {
            XmlDocument doc = new XmlDocument();
            String xml = HtmlToXml(html);
            doc.LoadXml(xml);
            return doc;
        }

        public static String HtmlToXml(String html)
        {
            String result = String.Format("<{1}>{0}</{1}>", html, MyXmlDocumentTagName);
            // Убираем неподходящие теги
            RemoveBadTags(ref result);
            //  0. Убираем комментарии
            RemoveComments(ref result);
            //  1. Creating HtmlDoc with content concluded in CDATA brackets
            HtmlDoc htmlDoc = CreateHtmlDoc(result);
            //  3. Все незакрытые теги закрыть с помощью '/' в конце тега
//            while(RemoveAllWrongClosing(htmlDoc))
            CloseTags(ref htmlDoc);
            //  4. & -> &amp;
            result = htmlDoc.ToString().Replace("&amp;", "&").Replace("&", "&amp;").Replace("'",String.Empty);
            return "<?xml version=\"1.0\"?>" + result;
        }

        private static void RemoveBadTags(ref String result)
        {
            Regex regex = new Regex("<.*?>");
            foreach (Match match in regex.Matches(result))
            {
                String tag = match.Value;
                if (tag[1] == '!')
                    result = result.Replace(tag, String.Empty);
            }
        }

        private static void RemoveComments(ref String result)
        {
            Int32 k = 0;
            while (k < result.Length - 5)
            {
                while (k < result.Length - 5 && result.Substring(k, 4) != "<!--")
                    k++;
                Int32 begin = k;
                while (k < result.Length - 4 && result.Substring(k, 3) != "-->")
                    k++;
                Int32 end = k + 3;
                String comment = result.Substring(begin, end - begin);
                if (comment.Length > 4 && comment.Substring(0, 4) == "<!--")
                {
                    result = result.Replace(comment, String.Empty);
                    k -= comment.Length - 1;
                }
            }
        }

        private static HtmlDoc CreateHtmlDoc(String html)
        {
            Int32 k = 0;
            HtmlDoc htmlDoc = new HtmlDoc();
            Tag previousTag = null;
            while (k < html.Length - 1)
            {
                Tag tag = Tag.GetNearestTag(html, ref k);   // Taking tag
                if (tag != null)
                {
                    htmlDoc.Add(tag.GetTagWithXmlStyleAttributes());                  // Adding to our doc
                    if (previousTag != null)                // If there was previous tag - taking content
                    {
                        Int32 length = tag.Begin - previousTag.End - 1;
                        if (length > 0)
                        {
                            String content = html.Substring(previousTag.End + 1, length);
                            htmlDoc.Add(String.Format("<![CDATA[{0}]]>", content));
                        }
                    }
                    previousTag = tag;                      // Determining previous tag
                }
            }
            return htmlDoc;
        }

        private static void AddCdataBrackets(ref String result)
        {
            Int32 k = 0;
            Tag beginTag = null;
            Tag endTag = null;
            while (k < result.Length - 1)
            {
                if (endTag == null)
                    beginTag = Tag.GetNearestTag(result, ref k);
                else
                    beginTag = endTag;
                endTag = Tag.GetNearestTag(result, ref k);

                Int32 contentBegin = beginTag.End + 1;
                Int32 contentEnd = endTag.Begin - 1;
                if (contentEnd - contentBegin + 1 > 0)
                {
                    String content = result.Substring(contentBegin, contentEnd - contentBegin + 1);
                    result = String.Format("{0}<![CDATA[{1}]]>{2}", result.Substring(0, beginTag.End + 1),
                                                                    content,
                                                                    result.Substring(endTag.Begin));
                    k = endTag.Begin + 12;
                    endTag = Tag.GetNearestTag(result, ref k);
                }
            }
        }

        private static void CloseTags(ref HtmlDoc htmlDoc)
        {
            List<String> checkedTagsNames = new List<string>();
            for(Int32 k = 0; k < htmlDoc.Count; k++)
            {
                Tag openTag = new Tag(htmlDoc[k]);
                // 1. Selecting tag
                while (k < htmlDoc.Count - 1 && (openTag == null
                                                || String.IsNullOrEmpty(openTag.Text)))
                {
                    k++;
                    openTag = new Tag(htmlDoc[k]);
                }
                if(!checkedTagsNames.Contains(openTag.Name.ToLower()))
                {
                    // 2. Finding all open/close tags with such name
                    List<Int32> openedTagIds = new List<Int32>();
                    List<Int32> oddClosingTagIds = new List<Int32>();
                    if (openTag.IsClosingTag)
                        oddClosingTagIds.Add(k);
                    else
                        openedTagIds.Add(k);
                    
                    String openTagName = openTag.Name.ToLower();
                    for (Int32 i = k + 1; i < htmlDoc.Count; i++)
                    {
                        Tag tmpTag = new Tag(htmlDoc[i]);
                        if (tmpTag.Name.ToLower() == openTagName)
                        {
                            if (tmpTag.IsClosingTag)                                // If is closing tag..
                            {
                                if (openedTagIds.Count > 0)                         // .. and there were opened tags
                                    openedTagIds.RemoveAt(openedTagIds.Count - 1);  // than removing it
                                else
                                    oddClosingTagIds.Add(i);                        // else it is odd closing tag
                            }
                            else                                                    // If it is opening tag
                            {
                                if (oddClosingTagIds.Count > 0)                     // .. and there were odd close tags
                                    oddClosingTagIds.RemoveAt(oddClosingTagIds.Count - 1);  // than remove odd tag
                                else
                                    openedTagIds.Add(i);                            // else adding to open tags
                            }
                        }
                    }
                    //  3. Closing all opening tags and clearing all odd closing tags
                    foreach (Int32 id in openedTagIds)
                        htmlDoc[id] = htmlDoc[id].Substring(0, htmlDoc[id].Length - 1) + " />";
                    foreach (Int32 id in oddClosingTagIds)
                        htmlDoc[id] = String.Empty;
                    checkedTagsNames.Add(openTagName);
                }
            }
        }

        private static Boolean RemoveAllWrongClosing(ref HtmlDoc htmlDoc)
        {
            //  If there were tags like <A1><B1></A1><B1>
            //  we should close them like <A1><B1 /></A1>
            //  <A><B><C></C></A></B> => <A><B /><C></C></A>
            return true;
        }

        public static string AdvancedTrim(String str)
        {
            str =  HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(str).Replace("\r", String.Empty).Replace('\n', ' '));

            return str;
        }
    }
}